<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>SPC 八大判异规则示例</title>
  <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
  <style>
    #charts {
      display: flex;
      flex-wrap: wrap;
    }
    .chart {
      width: 48%;
      height: 400px;
      margin: 1%;
    }
  </style>
</head>
<body>
  <h2>SPC 判异规则示例</h2>
  <div id="charts">
    <div id="chart1" class="chart"></div>
    <div id="chart2" class="chart"></div>
    <div id="chart3" class="chart"></div>
    <div id="chart4" class="chart"></div>
    <div id="chart5" class="chart"></div>
    <div id="chart6" class="chart"></div>
    <div id="chart7" class="chart"></div>
    <div id="chart8" class="chart"></div>
  </div>

  <script>
    // 工具函数
    function mean(arr) {
      return arr.reduce((a, b) => a + b, 0) / arr.length;
    }
    function stddev(arr, avg) {
      avg = avg || mean(arr);
      return Math.sqrt(arr.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / arr.length);
    }

    // 判异规则函数们

    const rules = {
      rule1(data, CL, sigma) {
        return data.map((d, i) => (Math.abs(d - CL) > 3 * sigma ? i : null)).filter(i => i !== null);
      },
      rule2(data, CL) {
        let res = [], count = 0, side = null;
        for (let i = 0; i < data.length; i++) {
          const currentSide = data[i] > CL ? 'up' : 'down';
          if (currentSide === side) {
            count++;
          } else {
            count = 1;
            side = currentSide;
          }
          if (count >= 7) {
            for (let j = i - 6; j <= i; j++) res.push(j);
          }
        }
        return [...new Set(res)];
      },
      rule3(data) {
        let res = [];
        for (let i = 0; i <= data.length - 6; i++) {
          let asc = true, desc = true;
          for (let j = i + 1; j < i + 6; j++) {
            if (data[j] <= data[j - 1]) asc = false;
            if (data[j] >= data[j - 1]) desc = false;
          }
          if (asc || desc) res.push(...Array.from({ length: 6 }, (_, k) => i + k));
        }
        return [...new Set(res)];
      },
      rule4(data, CL) {
        let res = [], lastSide = null, count = 0;
        for (let i = 0; i < data.length; i++) {
          const side = data[i] > CL ? 'up' : 'down';
          if (side !== lastSide) count++;
          else count = 1;
          lastSide = side;
          if (count >= 14) {
            for (let j = i - 13; j <= i; j++) res.push(j);
          }
        }
        return [...new Set(res)];
      },
      rule5(data, CL, sigma) {
        let res = [];
        for (let i = 0; i <= data.length - 3; i++) {
          let count = 0;
          for (let j = 0; j < 3; j++) {
            if (Math.abs(data[i + j] - CL) > 2 * sigma && (data[i + j] - CL) * (data[i] - CL) > 0) count++;
          }
          if (count >= 2) res.push(i, i + 1, i + 2);
        }
        return [...new Set(res)];
      },
      rule6(data, CL, sigma) {
        let res = [];
        for (let i = 0; i <= data.length - 5; i++) {
          let count = 0;
          for (let j = 0; j < 5; j++) {
            if (Math.abs(data[i + j] - CL) > sigma) count++;
          }
          if (count >= 4) res.push(...Array.from({ length: 5 }, (_, k) => i + k));
        }
        return [...new Set(res)];
      },
      rule7(data, CL, sigma) {
        let res = [];
        for (let i = 0; i <= data.length - 15; i++) {
          let allWithin = true;
          for (let j = 0; j < 15; j++) {
            if (Math.abs(data[i + j] - CL) > sigma) {
              allWithin = false;
              break;
            }
          }
          if (allWithin) res.push(...Array.from({ length: 15 }, (_, k) => i + k));
        }
        return [...new Set(res)];
      },
      rule8(data, CL, sigma) {
        let res = [];
        for (let i = 0; i <= data.length - 8; i++) {
          let allBeyond = true;
          for (let j = 0; j < 8; j++) {
            if (Math.abs(data[i + j] - CL) <= sigma) {
              allBeyond = false;
              break;
            }
          }
          if (allBeyond) res.push(...Array.from({ length: 8 }, (_, k) => i + k));
        }
        return [...new Set(res)];
      }
    };
    const rulesNames = [
        "任何点落在中心线（CL）上下3倍标准差（σ）之外。",                   rules.rule1,
        "连续七个点在中心线同一侧。",                                      rules.rule2,
        "​连续六个点呈稳步上升或下降趋势。",                                 rules.rule3,
        "连续十四个点在中心线两侧交替出现。",                               rules.rule4,
        "连续3点中有2点落在中心线同一侧，且距离中心线超过2个标准差（σ）。",   rules.rule5,
        "​连续五个点中有4个点与中心线相差超过一个标准差（σ）。",              rules.rule6,
        "连续十五个点，排列在中心线1个标准差（σ）范围内。",                  rules.rule7,
        "连续8个点，距离中心线超过1个标准差（σ）。",                        rules.rule8,
    ];
    // 示例数据
    const exampleData = [
      { name: "规则1", data: [10, 12, 10, 11, 35, 11, 12, 10, 11, 12, 11, 10, 12, 10, 11] },
      { name: "规则2", data: [10, 11, 12, 13, 14, 15, 16, 9, 10, 11, 10, 9, 10, 9, 10] },
      { name: "规则3", data: [10, 11, 12, 13, 14, 15, 10, 9, 8, 7, 6, 5, 10, 11, 12] },
      { name: "规则4", data: [10, 12, 9, 11, 10, 12, 9, 11, 10, 12, 9, 11, 10, 12, 9] },
      { name: "规则5", data: [12, 8.3, 8.2, 11, 11.5, 11, 12, 10, 11, 12, 11, 10, 12, 10, 11] },
      { name: "规则6", data: [10, 12, 10, 11, 11.5, 11, 12, 10, 11, 12, 11, 10, 12, 10, 11] },
      { name: "规则7", data: [10.1, 9.95, 10.05, 11, 10.05, 10.08, 9.98, 10.01, 10.03, 10.02, 10.05, 10.04, 9.96, 10.02, 10.00, 20, 25]},
      { name: "规则8", data: [8, 8.5, 8.3, 8.7, 8.8, 8.9, 9, 9.1, 10, 10.1, 10, 10, 10, 10, 10] }
    ];

    // 渲染图表
    exampleData.forEach((item, idx) => {
      const chart = echarts.init(document.getElementById(`chart${idx + 1}`));
      const avg = mean(item.data);
      const sigma = stddev(item.data, avg);
      const ruleFunc = rules[`rule${idx + 1}`];
      const outlierIndices = ruleFunc(item.data, avg, sigma);

      const series = [
        {
          name: '正常点',
          type: 'line',
          data: item.data.map((v, i) => outlierIndices.includes(i) ? null : v),
          lineStyle: { color: '#000' },
          itemStyle: { color: '#000' },
        },
        {
          name: '异常点',
          type: 'scatter',
          data: item.data.map((v, i) => outlierIndices.includes(i) ? v : null),
          itemStyle: { color: 'red' },
          symbolSize: 10,
        }
      ];

      const markLine = {
        silent: true,
        lineStyle: { type: 'dashed', color: '#aaa' },
        data: [
          { yAxis: avg, label: { formatter: 'CL' } },
          { yAxis: avg + 3 * sigma, label: { formatter: 'UCL' } },
          { yAxis: avg - 3 * sigma, label: { formatter: 'LCL' } }
        ]
      };

      chart.setOption({
        title: { text: item.name+"方差sigma："+sigma +"  CL:"+ avg},
        tooltip: {},
        xAxis: { type: 'category', data: item.data.map((_, i) => i + 1) },
        yAxis: { type: 'value' },
        series: series.map(s => ({ ...s, markLine })),
      });
    });
  </script>
</body>
</html>
